/*
 * Copyright 2008 the Breachwalls OSS project and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.breachwalls.mogen.xml.parser;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;

import com.breachwalls.mogen.xml.bean.GenerateModels;
import com.breachwalls.mogen.xml.bean.ModelClass;
import com.breachwalls.mogen.xml.bean.ModelClassProperty;
import com.breachwalls.mogen.xml.context.ModelParseContext;

/**
 * 自動生成の定義を読み取るためのパーサ.
 * @author todoken
 * @version $Revision: 31 $
 * @see ModelGenerationContext
 * @see ModelGenerationDocumentParser
 */
public class DefaultModelGenerationDocumentParser implements ModelGenerationDocumentParser {

    /**
     * ルートエレメント.
     */
    private static final String ROOT_ELEMENT = "generate-models";

    /**
     * 自動生成モードの属性.
     */
    private static final String GENERATE_MODE_ATTRIBUTE = "mode";

    /**
     * モデルクラスエレメント.
     */
    private static final String MODEL_CLASS_ELEMENT = "model-class";

    /**
     * 名称属性.
     */
    private static final String NAME_ATTRIBUTE = "name";

    /**
     * 抽象クラス属性.
     */
    private static final String ABSTRACT_ATTRIBUTE = "abstract";

    /**
     * 親クラス属性.
     */
    private static final String PARENT_ATTRIBUTE = "parent";

    /**
     * モデルタイプ属性.
     */
    private static final String TYPE_ATTRIBUTE = "type";

    /**
     * クラス属性.
     */
    private static final String CLASS_ATTRIBUTE = "class";

    /**
     * プロパティエレメント.
     */
    private static final String PROPERTY_ELEMENT = "property";

    /**
     * 値属性.
     */
    private static final String VALUE_ATTRIBUTE = "value";

    /**
     * 説明エレメント.
     */
    private static final String DESCRIPTION_ELEMENT = "description";

    /**
     * スペック属性.
     */
    private static final String SPEC_ATTRIBUTE = "spec";

    /**
     * データタイプエレメントのXPATH表現.
     */
    private static final String ROOT_XPATH_EXPRESSION = "/" + ROOT_ELEMENT;

    /**
     * {@inheritDoc}
     */
    public void parse(Document document, ModelParseContext context) {
        final Element root = (Element) document.selectSingleNode(ROOT_XPATH_EXPRESSION);
        final String mode = root.attributeValue(GENERATE_MODE_ATTRIBUTE);

        GenerateModels models = new GenerateModels();
        models.setMode(StringUtils.upperCase(mode));
        parseModelClass(root, models);

        context.register(models);
    }

    /**
     * モデルクラスのXML定義をパースする.
     * @param root ルートのDOMエレメント
     * @param models 自動生成するモデルのXML定義
     */
    protected void parseModelClass(final Element root, final GenerateModels models) {
        List<ModelClass> modelClasses = new ArrayList<ModelClass>();

        for (Object o : root.selectNodes(ROOT_XPATH_EXPRESSION + "/" + MODEL_CLASS_ELEMENT)) {
            final Element element = (Element) o;
            final String desc = element.elementText(DESCRIPTION_ELEMENT);
            final String name = element.attributeValue(NAME_ATTRIBUTE);
            final String parent = element.attributeValue(PARENT_ATTRIBUTE);
            final String abst = element.attributeValue(ABSTRACT_ATTRIBUTE);
            final String clazz = element.attributeValue(CLASS_ATTRIBUTE);
            final String type = element.attributeValue(TYPE_ATTRIBUTE);

            ModelClass model = new ModelClass();
            model.setDescription(desc);
            model.setName(name);
            model.setParent(parent);
            model.setAbstractClass(abst);
            model.setClazz(clazz);
            model.setType(StringUtils.upperCase(type));

            parseModelClassProperty(element, model);

            modelClasses.add(model);
        }

        models.setModelClasses(modelClasses);
    }

    /**
     * モデルクラスのプロパティ定義をパースする.
     * @param root パースするモデルクラスのエレメント
     * @param model モデルクラスのXML定義
     */
    protected void parseModelClassProperty(final Element root, final ModelClass model) {
        List<ModelClassProperty> props = new ArrayList<ModelClassProperty>();

        for (Object o : root.selectNodes(PROPERTY_ELEMENT)) {
            final Element element = (Element) o;
            final String desc = element.attributeValue(DESCRIPTION_ELEMENT);
            final String name = element.attributeValue(NAME_ATTRIBUTE);
            final String value = element.attributeValue(VALUE_ATTRIBUTE);
            final String type = element.attributeValue(TYPE_ATTRIBUTE);
            final String spec = element.attributeValue(SPEC_ATTRIBUTE);

            ModelClassProperty property = new ModelClassProperty();
            property.setDescription(desc);
            property.setName(name);
            property.setValue(value);
            property.setType(StringUtils.upperCase(type));
            property.setSpecs(toUpperCaseSplit(spec, ','));

            props.add(property);
        }

        model.setModelClassProperties(props);
    }

    /**
     * 文字列を区切り文字で区切り、全てアルファベットの大文字にして変換する.
     * @param value 文字列
     * @param separator 区切り文字
     * @return 区切り文字で区切り、全てアルファベットの大文字にした文字配列
     */
    protected String[] toUpperCaseSplit(String value, char separator) {
        String[] values = StringUtils.split(value, separator);
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                values[i] = StringUtils.upperCase(values[i]);
            }
        }
        return values;
    }

}
